package checker

import (
	"mi/compiler/ast"
	"mi/compiler/diagnostic"
	"mi/compiler/scanner"
	"mi/compiler/token"
	"mi/compiler/utilities"
	"mi/internal/span"
	"mi/internal/xbase"
	"mi/internal/xpath"
)

// GRAMMAR CHECKING
func (c *checker) checkGrammarAnnotations(node ast.Node)  bool {
	if node.GetAnnotations() == nil {
		return false
	}
	if !ast.NodeCanBeDecorated(node) {
		return c.grammarErrorOnFirstToken(node, diagnostic.M_Annotation_are_not_valid_here)
	}
	return false
}

func (c *checker) checkGrammarModifiers(node ast.Node)  bool {
	var quickResult = c.reportObviousModifierErrors(node)
	if quickResult != nil {
		return *quickResult
	}

	var lastStatic *ast.Modifier
	// var lastDeclare *types.Modifier

	var flags = ast.MFNone
	var modifiers = node.GetModifiers()
	for i := 0; i < modifiers.Len(); i ++ {
		var modifier = modifiers.At(i).(*ast.Modifier)
		switch modifier.Token {
		case token.PublicKeyword,
			token.ProtectedKeyword,
			token.PrivateKeyword:
			var text = modifier.Token.ToString()
			if flags&ast.MFAccessibilityModifier != 0 {
				return c.grammarErrorOnNode(modifier, diagnostic.M_Accessibility_modifier_already_seen)
			} else if flags&ast.MFStatic != 0 {
				return c.grammarErrorOnNode(modifier, diagnostic.M_0_modifier_must_precede_1_modifier, text, token.StaticKeyword.ToString())
			} else if flags&ast.MFAbstract != 0 {
				if modifier.Token == token.PrivateKeyword {
					return c.grammarErrorOnNode(modifier, diagnostic.M_0_modifier_cannot_be_used_with_1_modifier, text, token.AbstractKeyword.ToString())
				} else {
					return c.grammarErrorOnNode(modifier, diagnostic.M_0_modifier_must_precede_1_modifier, text, token.AbstractKeyword.ToString())
				}
			}
			flags |= ast.ModifierToFlag(modifier.Token)

		case token.StaticKeyword:
			if flags&ast.MFStatic != 0 {
				return c.grammarErrorOnNode(modifier, diagnostic.M_0_modifier_already_seen, token.StaticKeyword.ToString())
			} else if ast.IsSourceFile(node.Parent()) {
				return c.grammarErrorOnNode(modifier, diagnostic.M_0_modifier_cannot_appear_on_a_module_or_namespace_element, token.StaticKeyword.ToString())
			} else if ast.IsParameterDeclaration(node) {
				return c.grammarErrorOnNode(modifier, diagnostic.M_0_modifier_cannot_appear_on_a_parameter, token.StaticKeyword.ToString())
			} else if flags&ast.MFAbstract != 0 {
				return c.grammarErrorOnNode(modifier, diagnostic.M_0_modifier_cannot_be_used_with_1_modifier, token.StaticKeyword.ToString(), token.AbstractKeyword.ToString())
			}
			flags |= ast.MFStatic
			lastStatic = modifier

		case token.AbstractKeyword:
			if flags&ast.MFAbstract != 0 {
				return c.grammarErrorOnNode(modifier, diagnostic.M_0_modifier_already_seen, token.AbstractKeyword.ToString())
			}
			if !ast.IsClassDeclaration(node) {
				if!ast.IsMethodDeclaration(node) {
					return c.grammarErrorOnNode(modifier, diagnostic.M_abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration)
				}
				if !(ast.IsClassDeclaration(node.Parent()) && ast.GetModifierFlags(node.Parent())&ast.MFAbstract != 0) {
					return c.grammarErrorOnNode(modifier, diagnostic.M_Abstract_methods_can_only_appear_within_an_abstract_class)
				}
				if flags&ast.MFStatic != 0 {
					return c.grammarErrorOnNode(modifier, diagnostic.M_0_modifier_cannot_be_used_with_1_modifier, token.StaticKeyword.ToString(), token.AbstractKeyword.ToString())
				}
				if flags&ast.MFPrivate != 0 {
					return c.grammarErrorOnNode(modifier, diagnostic.M_0_modifier_cannot_be_used_with_1_modifier, token.PrivateKeyword.ToString(), token.AbstractKeyword.ToString())
				}
			}

			flags |= ast.MFAbstract
		}
	}

	if ast.IsConstructorDeclaration(node) {
		if flags&ast.MFStatic != 0 {
			return c.grammarErrorOnNode(lastStatic, diagnostic.M_0_modifier_cannot_appear_on_a_constructor_declaration, token.StaticKeyword.ToString())
		}
		if flags&ast.MFAbstract != 0 {
			return c.grammarErrorOnNode(lastStatic, diagnostic.M_0_modifier_cannot_appear_on_a_constructor_declaration, token.AbstractKeyword.ToString())
		} 
		return false
	}

	//else if ast.Kind == token.ImportDeclaration && flags&ast.MFAmbient != 0 {
	//	return c.grammarErrorOnNode(lastDeclare, diagnostic.M_A_0_modifier_cannot_be_used_with_an_import_declaration, "declare")
	//} else if ast.Kind == token.Parameter && (flags&ast.MFParameterPropertyModifier != 0) && ast.ToParameterDeclaration().DotDotDotToken != nil {
	//	return c.grammarErrorOnNode(ast, diagnostic.M_A_parameter_property_cannot_be_declared_using_a_rest_parameter)
	//}
	return false
}

/**
 * true | false: Early return this value from checkGrammarModifiers.
 * nil: Need to do full checking on the modifiers.
 */
func (c *checker) reportObviousModifierErrors(node ast.Node) *bool {
	if node.GetModifiers() == nil {
		return xbase.NewBool(false)
	}
	if c.shouldReportBadModifier(node){
		return xbase.NewBool(c.grammarErrorOnFirstToken(node, diagnostic.M_Modifiers_cannot_appear_here))
	}
	return nil
}

func (c *checker) shouldReportBadModifier(node ast.Node)  bool {
	switch node.(type) {
	case *ast.ClassDeclaration,
		*ast.InterfaceDeclaration,
		*ast.EnumDeclaration,
		*ast.AnnotationDeclaration,
		*ast.FieldDeclaration,
		*ast.MethodDeclaration,
		*ast.ParameterDeclaration,
		*ast.VariableDeclaration:
		return false
	default:
		return true
	}

	//switch node.Kind {
	//	case token.PropertyDeclaration,
	//	 	token.MethodDeclaration,
	//	 	token.ImportDeclaration,
	//	 	token.ArrowFunction,
	//	 	token.Parameter:
	//		return false
	//	default:
	//		if node.Parent.Kind == token.SourceFile {
	//			return false
	//		}
	//		switch node.Kind {
	//			case token.ClassDeclaration:
	//				return c.nodeHasAnyModifiersExcept(node, token.AbstractKeyword)
	//			case token.InterfaceDeclaration,
	//			 	token.VariableStatement:
	//				return true
	//			case token.EnumDeclaration:
	//				return c.nodeHasAnyModifiersExcept(node, token.ConstKeyword)
	//			default:
	//				debug.Fail("")
	//				return false
	//		}
	//}
}

//func (c *checker) nodeHasAnyModifiersExcept(node *ast.Node, allowedModifier token.Token)  bool {
//	return len(node.Modifiers.GetArray()) > 1 || node.Modifiers.GetArray()[0].Kind != allowedModifier
//}

//func (c *checker) checkGrammarForDisallowedTrailingComma(list ast.List)  bool {
//	if list != nil && list.HasTrailingComma {
//		var start = list.End - len(",")
//		var end = list.End
//		var sourceFile = util.GetSourceFileOfNode(list.GetArray()[0])
//		return c.grammarErrorAtPos(sourceFile, start, end - start, diagnostic.M_Trailing_comma_not_allowed)
//	}
//	return false
//}

func (c *checker) checkGrammarTypeParameterList(typeParameters *ast.DeclarationList, file *ast.SourceFile)  bool {
	//if c.checkGrammarForDisallowedTrailingComma(typeParameters.ToNodeArray()) {
	//	return true
	//}

	if typeParameters != nil && typeParameters.Len() == 0 {
		var start = typeParameters.Pos() - len("<")
		var end = scanner.SkipTrivia(file.Text, typeParameters.End(), false, false) + len(">")
		return c.grammarErrorAtPos(file, start, end - start, diagnostic.M_Type_parameter_list_cannot_be_empty)
	}
	
	return false
}

func (c *checker) checkGrammarParameterList(parameters *ast.DeclarationList) bool {
	if parameters != nil {
		var parameterCount = parameters.Len()
		for i := 0; i < parameterCount; i++ {
			var parameter = parameters.At(i).(*ast.ParameterDeclaration)
			if parameter.Dots != nil {
				if i != (parameterCount - 1) {
					return c.grammarErrorOnNode(parameter.Dots, diagnostic.M_A_rest_parameter_must_be_last_in_a_parameter_list)
				}

				if parameter.Initializer != nil {
					return c.grammarErrorOnNode(parameter.Name, diagnostic.M_A_parameter_cannot_have_an_initializer)
				}
			}
		}
	}
	return false
}

func (c *checker) checkGrammarFunctionLikeDeclaration(node ast.FunctionLike)  bool {
	// Prevent cascading error by short-circuit
	var file = ast.GetSourceFileOfNode(node)
	return c.checkGrammarAnnotations(node) || c.checkGrammarModifiers(node) || c.checkGrammarTypeParameterList(node.GetTypeParameters(), file) ||
		c.checkGrammarParameterList(node.GetParameters()) || c.checkGrammarArrowFunction(node, file)
}

func (c *checker) checkGrammarArrowFunction(node ast.FunctionLike, file *ast.SourceFile)  bool {
	if arrowFunction, ok := node.(*ast.ArrowFunctionDeclaration); ok {
		var startLine = scanner.GetFileLineAndCharacterFromPosition(file, arrowFunction.Arrow.Pos()).Line
		var endLine = scanner.GetFileLineAndCharacterFromPosition(file, arrowFunction.Arrow.End()).Line
		if startLine != endLine {
			return c.grammarErrorOnNode(arrowFunction.Arrow, diagnostic.M_Line_terminator_not_permitted_before_arrow)
		}

		// Arrow function declaration can not has type parameter
		var typeParameters = node.GetTypeParameters()
		for _, declaration := range typeParameters.Array() {
			var typeParameter = declaration.(*ast.TypeParameterDeclaration)
			c.grammarErrorOnNode(typeParameter, diagnostic.M_Arrow_functions_are_not_allowed_to_have_type_parameters)
		}
	}
	return false
}

func (c *checker) checkGrammarForAtLeastOneTypeArgument(node ast.Node, typeArguments *ast.TypeList)  bool {
	if typeArguments != nil && typeArguments.Len() == 0 {
		var sourceFile = ast.GetSourceFileOfNode(node)
		var start = typeArguments.Pos() - len("<")
		var end = scanner.SkipTrivia(sourceFile.Text, typeArguments.End(), false, false) + len(">")
		return c.grammarErrorAtPos(sourceFile, start, end - start, diagnostic.M_Type_argument_list_cannot_be_empty)
	}
	return false
}

func (c *checker) checkGrammarTypeArguments(node ast.Node, typeArguments *ast.TypeList)  bool {
	return c.checkGrammarForAtLeastOneTypeArgument(node, typeArguments)
	//return c.checkGrammarForDisallowedTrailingComma(typeArguments.ToNodeArray()) ||
	//	c.checkGrammarForAtLeastOneTypeArgument(node, typeArguments)
}

func (c *checker) checkGrammarForOmittedArgument(node ast.Expression, args *ast.ExpressionList)  bool {
	if args != nil {
		var sourceFile = ast.GetSourceFileOfNode(node)
		for _, arg := range args.Array() {
			if ast.IsOmittedExpression(arg) {
				return c.grammarErrorAtPos(sourceFile, arg.Pos(), 0, diagnostic.M_Argument_expression_expected)
			}
		}
	}
	return false
}

func (c *checker) checkGrammarArguments(node ast.Expression, args *ast.ExpressionList)  bool {
	return c.checkGrammarForOmittedArgument(node, args)
}

func (c *checker) checkGrammarHeritageClause(node *ast.HeritageClause)  bool {
	var types_ = node.Types
	//if c.checkGrammarForDisallowedTrailingComma(types_.ToNodeArray()) {
	//	return true
	//}
	if types_ != nil && types_.Len() == 0 {
		var listType = node.Token.ToString()
		var sourceFile = ast.GetSourceFileOfNode(node)
		return c.grammarErrorAtPos(sourceFile, types_.Pos(), 0, diagnostic.M_0_list_cannot_be_empty, listType)
	}

	return false
}

func (c *checker) checkGrammarClassDeclarationHeritageClauses(node ast.ObjectLike) bool {
	var seenExtendsClause = false
	var seenImplementsClause = false

	if ast.IsTopLevelDeclaration(node) && ast.IsClassDeclaration(node) {
		if ast.GetModifierFlags(node)&ast.MFPublic != 0 {
			var sourceFile = ast.GetSourceFileOfNode(node)
			var nameNoExtension = xpath.GetBaseFileNameNoExtension(sourceFile.Uri.Filename())
			var declarationName = ast.GetDeclarationName(node)
			if declarationName.Value != nameNoExtension {
				return c.grammarErrorOnNode(declarationName, diagnostic.M_Class_0_is_public_should_be_declared_in_a_file_named_0_mi, declarationName.Value)
			}
		}
	}

	if !c.checkGrammarAnnotations(node) && !c.checkGrammarModifiers(node) && node.GetHeritageClauses() != nil {
		var heritageClauses = node.GetHeritageClauses()
		for i := 0; i < heritageClauses.Len(); i ++ {
			var heritageClause = heritageClauses.At(i).(*ast.HeritageClause)
			if heritageClause.Token == token.ExtendsKeyword {
				if seenExtendsClause {
					return c.grammarErrorOnFirstToken(heritageClause, diagnostic.M_extends_clause_already_seen)
				}

				if seenImplementsClause {
					return c.grammarErrorOnFirstToken(heritageClause, diagnostic.M_extends_clause_must_precede_implements_clause)
				}

				if heritageClause.Types.Len() > 1 {
					return c.grammarErrorOnFirstToken(heritageClause.Types.At(1), diagnostic.M_Classes_can_only_extend_a_single_class)
				}

				seenExtendsClause = true
			} else {
				if heritageClause.Token != token.ImplementsKeyword {
					panic("Here heritage clause token must is implement.")
				}
				if seenImplementsClause {
					return c.grammarErrorOnFirstToken(heritageClause, diagnostic.M_implements_clause_already_seen)
				}

				seenImplementsClause = true
			}

			// Grammar checking heritageClause inside class declaration
			c.checkGrammarHeritageClause(heritageClause)
		}
	}

	return false
}

func (c *checker) checkGrammarEnumDeclaration(node *ast.EnumDeclaration) bool {
	var hasError = false
	if heritageClauses := node.GetHeritageClauses(); heritageClauses != nil {
		for i := 0; i < heritageClauses.Len(); i ++ {
			var heritageClause = heritageClauses.At(i).(*ast.HeritageClause)
			if heritageClause.Token != token.ExtendsKeyword {
				continue
			}

			if heritageClause.Types.Len() > 0 {
				c.grammarErrorOnFirstToken(heritageClause.Types.At(0), diagnostic.M_Enum_can_not_has_extends)
				hasError = true
			}

			hasError = hasError || c.checkGrammarHeritageClause(heritageClause)
		}
	}

	if node.TypeParameters != nil {
		var sourceFile = ast.GetSourceFileOfNode(node)
		c.grammarErrorAtPos(sourceFile, node.TypeParameters.Pos(), node.TypeParameters.End()-node.TypeParameters.Pos(),
			diagnostic.M_Enum_Cannot_has_type_parameters)
		hasError = true
	}

	return hasError
}

func (c *checker) checkGrammarInterfaceDeclaration(node *ast.InterfaceDeclaration) bool {
	var seenExtendsClause = false

	if node.HeritageClauses != nil {
		var heritageClauses = node.GetHeritageClauses()
		for i := 0; i < heritageClauses.Len(); i ++ {
			var heritageClause = heritageClauses.At(i).(*ast.HeritageClause)
			if heritageClause.Token == token.ExtendsKeyword {
				if seenExtendsClause {
					return c.grammarErrorOnFirstToken(heritageClause, diagnostic.M_extends_clause_already_seen)
				}

				seenExtendsClause = true
			} else {
				if heritageClause.Token != token.ImplementsKeyword {
					panic("Here heritage clause token must is implement.")
				}
				return c.grammarErrorOnFirstToken(heritageClause, diagnostic.M_Interface_declaration_cannot_have_implements_clause)
			}

			// Grammar checking heritageClause inside class declaration
			c.checkGrammarHeritageClause(heritageClause)
		}
	}
	return false
}

func (c *checker) checkGrammarAnnotationDeclaration(node *ast.AnnotationDeclaration) bool {
	var hasError = false
	if heritageClauses := node.GetHeritageClauses(); heritageClauses != nil {
		for i := 0; i < heritageClauses.Len(); i ++ {
			var heritageClause = heritageClauses.At(i).(*ast.HeritageClause)
			var message *diagnostic.Message
			if heritageClause.Token == token.ExtendsKeyword {
				message = diagnostic.M_Annotation_can_not_has_extends
			} else {
				message = diagnostic.M_Annotation_can_not_has_implement_list
			}
			if heritageClause.Types.Len() > 0 {
				c.grammarErrorOnFirstToken(heritageClause.Types.At(0), message)
				hasError = true
			}

			hasError = hasError || c.checkGrammarHeritageClause(heritageClause)
		}
	}

	if node.TypeParameters != nil {
		var sourceFile = ast.GetSourceFileOfNode(node)
		c.grammarErrorAtPos(sourceFile, node.TypeParameters.Pos(), node.TypeParameters.End()-node.TypeParameters.Pos(),
			diagnostic.M_Annotation_Cannot_has_type_parameters)
		hasError = true
	}

	return hasError
}

func (c *checker) checkGrammarForInvalidQuestionMark(questionToken ast.Node, message *diagnostic.Message)  bool {
	if questionToken != nil {
		return c.grammarErrorOnNode(questionToken, message)
	}

	return false
}

func (c *checker) checkGrammarForInStatement(forEachStatement *ast.ForOfStatement)  bool {
	if c.checkGrammarStatementInAmbientContext(forEachStatement) {
		return true
	}

	var initializer = forEachStatement.VariableDeclaration.Initializer
	if !isNull(initializer) {
		return c.grammarErrorOnNode(initializer, diagnostic.M_The_variable_declaration_of_a_for_in_statement_cannot_have_an_initializer)
	}

	return false
}

func (c *checker) checkGrammarMethod(node *ast.MethodDeclaration) bool {
	if c.checkGrammarFunctionLikeDeclaration(node) {
		return true
	}

	var hasError = false
	// Annotation method not allow type parameter or parameter
	if ast.IsAnnotationDeclaration(node.Parent()) {
		for _, declaration := range node.TypeParameters.Array() {
			hasError = true
			c.grammarErrorOnNode(declaration, diagnostic.M_The_method_in_the_annotation_cannot_have_type_parameters)
		}
		for _, declaration := range node.Parameters.Array() {
			hasError = true
			c.grammarErrorOnNode(declaration, diagnostic.M_The_method_in_the_annotation_cannot_have_parameters)
		}
	}

	var flags = c.getDeclarationModifierFlagsFromDeclaration(node)
	var needBody = ast.IsInterfaceLike(node.Parent()) && flags&(ast.MFDefault|ast.MFStatic) != 0 ||
			ast.IsClassLike(node.Parent()) && flags&ast.MFAbstract == 0

	if needBody && isNull(node.Body) {
		var sourceFile = ast.GetSourceFileOfNode(node)
		if !sourceFile.IsDeclarationFile && node.Body == nil {
			c.grammarErrorOnNode(node, diagnostic.M_Missing_function_body)
			return true
		}
	}

	if !needBody && !isNull(node.Body) {
		c.grammarErrorOnNode(node.Body, diagnostic.M_Abstract_method_not_allow_body)
		return true
	}

	return hasError
}

func (c *checker) checkGrammarBreakOrContinueStatement(node *ast.BranchStatement)  bool {
	var current ast.Node = node
	for current != nil {
		if ast.IsFunctionLike(current) {
			return c.grammarErrorOnNode(node, diagnostic.M_Jump_target_cannot_cross_function_boundary)
		}

		switch n := current.(type) {
		case *ast.LabeledStatement:
			if node.Label != nil && n.Label.Value == node.Label.Value {
				// found matching label - verify that label usage is correct
				// continue can only target labels that are on iteration statements
				var isMisplacedContinueLabel = node.Token == token.ContinueKeyword &&
					!ast.IsIterationStatement(n.Statement, true)

				if isMisplacedContinueLabel {
					return c.grammarErrorOnNode(node, diagnostic.M_A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement)
				}

				return false
			}
		case *ast.SwitchStatement:
			if node.Token == token.BreakKeyword && node.Label == nil {
				// unlabeled break within switch statement - ok
				return false
			}
		default:
			if ast.IsIterationStatement(current, false) && node.Label == nil {
				// unlabeled break or continue within iteration statement - ok
				return false
			}
		}

		current = current.Parent()
	}

	if node.Label != nil {
		if node.Token == token.BreakKeyword {
			return c.grammarErrorOnNode(node, diagnostic.M_A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement)
		} else {
			return c.grammarErrorOnNode(node, diagnostic.M_A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement)
		}
	} else {
		if node.Token == token.BreakKeyword {
			return c.grammarErrorOnNode(node, diagnostic.M_A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement)
		} else {
			return c.grammarErrorOnNode(node, diagnostic.M_A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement)
		}
	}
}

func (c *checker) checkGrammarVariableLikeDeclaration(node ast.VariableLike) bool {
	if isNull(node.GetType()) && isNull(node.GetInitializer()) && !ast.IsForOfStatement(node.Parent()) {
		var sourceFile = ast.GetSourceFileOfNode(node)
		c.grammarErrorAtPos(sourceFile, node.End(), 1, diagnostic.M_Type_or_Initializer_expected)
		return true
	}

	return false
}

func (c *checker) hasParseDiagnostics(sourceFile *ast.SourceFile)  bool {
	return len(diagnostic.GetSourceFileDiagnostics(sourceFile, diagnostic.ParseDiagnostics)) > 0
}

func (c *checker) grammarErrorOnFirstToken(node ast.Node, message *diagnostic.Message, args ... interface{})  bool {
	var sourceFile = ast.GetSourceFileOfNode(node)
	if !c.hasParseDiagnostics(sourceFile) {
		var textSpan = utilities.GetSpanOfTokenAtPosition(sourceFile, node.Pos())
		c.diagnostics = append(c.diagnostics, diagnostic.CreateFileDiagnostic(
			sourceFile, textSpan.Start, textSpan.Length, message, args...))
		return true
	}
	return false
}

func (c *checker) grammarErrorAtPos(sourceFile *ast.SourceFile, start int, length int, message *diagnostic.Message, args ... interface{})  bool {
	if !c.hasParseDiagnostics(sourceFile) {
		c.diagnostics = append(c.diagnostics, diagnostic.CreateFileDiagnostic(sourceFile, start, length, message, args...))
		return true
	}
	return false
}

func (c *checker) grammarErrorOnNode(node ast.Node, message *diagnostic.Message, args ... interface{})  bool {
	var sourceFile = ast.GetSourceFileOfNode(node)
	if !c.hasParseDiagnostics(sourceFile) {
		c.diagnostics = append(c.diagnostics, utilities.CreateDiagnosticForNode(node, message, args...))
		return true
	}
	return false
}

func (c *checker) checkGrammarConstructorTypeParameters(node *ast.ConstructorDeclaration) bool {
	if node.TypeParameters != nil {
		return c.grammarErrorAtPos(ast.GetSourceFileOfNode(node), node.TypeParameters.Pos(),
			node.TypeParameters.End() - node.TypeParameters.Pos(), diagnostic.M_Type_parameters_cannot_appear_on_a_constructor_declaration)
	}
	return false
}

func (c *checker) checkGrammarConstructorTypeAnnotation(node *ast.ConstructorDeclaration) bool {
	if node.Type != nil {
		return c.grammarErrorOnNode(node.Type, diagnostic.M_Type_annotation_cannot_appear_on_a_constructor_declaration)
	}
	return false
}

func (c *checker) checkGrammarField(_ *ast.FieldDeclaration) bool {
	//if ast.IsInAmbientContext(node) && node.Initializer != nil {
	//	return c.grammarErrorOnFirstToken(node.Initializer, diagnostic.M_Initializers_are_not_allowed_in_ambient_contexts)
	//}

	return false
}

func (c *checker) checkGrammarStatementInAmbientContext(node ast.Node)  bool {
	if ast.IsInAmbientContext(node) {

		// Find containing block which is either Block, ModuleBlock, SourceFile
		var links = c.getNodeLinks(node)
		if links.hasReportedStatementInAmbientContext == nil && ast.IsFunctionLike(node.Parent()) {
			var res = c.grammarErrorOnFirstToken(node, diagnostic.M_An_implementation_cannot_be_declared_in_ambient_contexts)
			c.getNodeLinks(node).hasReportedStatementInAmbientContext = xbase.NewBool(res)
			return *c.getNodeLinks(node).hasReportedStatementInAmbientContext
		}

		// We are either parented by another statement, or some sort of block.
		// If we're in a block, we only want to really report an error once
		// to prevent noisiness.  So use a bit on the block to indicate if
		// this has already been reported, and don't report if it has.
		//
		if ast.IsBlockStatement(node.Parent()) || ast.IsSourceFile(node.Parent()){
			var links = c.getNodeLinks(node.Parent())
			// Check if the containing block ever report this error
			if links.hasReportedStatementInAmbientContext == nil {
				var res = c.grammarErrorOnFirstToken(node, diagnostic.M_Statements_are_not_allowed_in_ambient_contexts)
				links.hasReportedStatementInAmbientContext = xbase.NewBool(res)
				return *links.hasReportedStatementInAmbientContext
			}
		} else {
			// We must be parented by a statement.  If so, there's no need
			// to report the error as our parent will have already done it.
			// Debug.assert(isStatement(ast.parent))
		}
	}

	return false
}

func (c *checker) checkGrammarExpressionStatement(node *ast.ExpressionStatement)  bool {
	if !ast.IsLegalExpressionStatement(node) {

		var sourceFile = ast.GetSourceFileOfNode(node)
		var pos = scanner.SkipTrivia(sourceFile.Text, node.Pos(), false, false)
		c.grammarErrorAtPos(sourceFile, pos, node.End()-pos, diagnostic.M_Not_a_statement)
		return true
	}

	return false
}

func (c *checker) checkGrammarNumericLikeLiteral(_ *ast.LiteralExpression) bool {
	//switch node.Kind {
	//case token.CharLiteral:
	//	if len(node.Text) == 0 {
	//		c.grammarErrorOnNode(node, diagnostic.M_Empty_character_literal)
	//		return true
	//	} else if utf8.RuneCountInString(node.Text) > 1 {
	//		c.grammarErrorOnNode(node, diagnostic.M_Too_many_characters_in_character_literal)
	//		return true
	//	}
	//case token.IntLiteral:
	//	var value = c.getValueFormNumberLikeLiteralText(ast.Text)
	//	if !util.InIntRange(value) {
	//		c.grammarErrorOnNode(ast, diagnostic.M_0_Value_is_out_of_range, types.TokenToString(token.IntKeyword))
	//		return true
	//	}
	//case token.LongLiteral:
	//	var value = c.getValueFormNumberLikeLiteralText(ast.Text)
	//	if !util.InLongRange(value) {
	//		c.grammarErrorOnNode(ast, diagnostic.M_0_Value_is_out_of_range, types.TokenToString(token.LongKeyword))
	//		return true
	//	}
	//case token.FloatLiteral:
	//	var value = c.getValueFormNumberLikeLiteralText(ast.Text)
	//	if !util.InFloatRange(value) {
	//		c.grammarErrorOnNode(ast, diagnostic.M_0_Value_is_out_of_range, types.TokenToString(token.FloatKeyword))
	//		return true
	//	}
	//case token.DoubleLiteral:
	//	var value = c.getValueFormNumberLikeLiteralText(ast.Text)
	//	if !util.InDoubleRange(value) {
	//		c.grammarErrorOnNode(ast, diagnostic.M_0_Value_is_out_of_range, types.TokenToString(token.DoubleKeyword))
	//		return true
	//	}
	//}
	return false
}

func (c *checker) checkGrammarNewObjectExpression(_ *ast.NewObjectExpression) bool {
	return false
}

func (c *checker) checkGrammarNewArrayExpression(node *ast.NewArrayExpression) bool {
	if !ast.IsArrayType(node.Type) {
		return false
	}

	switch node.Type.(type) {
	case *ast.IntrinsicType:
		// new int[]{1, 2, 3}
		// new int
		//        ^ Missing '['
		c.grammarErrorOnNode(node.Type, diagnostic.M_0_expected, "[")
		return true

	case *ast.UnionType:
		c.grammarErrorOnNode(node.Type, diagnostic.M_Union_types_are_not_allowed_here)
		return true

	case *ast.IntersectionType:
		c.grammarErrorOnNode(node.Type, diagnostic.M_Intersection_Type_types_are_not_allowed_here)
		return true
		
	case *ast.ArrayType:
		if isNull(node.Initializer) {
			if !ast.IsArrayTypeHasDimension(node.Type.(*ast.ArrayType)) {
				c.grammarErrorOnNode(node.Type, diagnostic.M_Missing_dimension_or_initialization)
				return true
			}
		} else {
			if ast.IsArrayTypeHasDimension(node.Type.(*ast.ArrayType)) {
				c.error(node.Initializer, diagnostic.M_Cannot_define_dimension_expressions_when_an_array_initializer_is_provided)
				return true
			}
		}
	}

	return false
}

func (c *checker) checkGrammarArrayType(node *ast.ArrayType) bool {
	if !isNull(node.Dimension) {
		if !ast.IsNewArrayType(node) {
			c.grammarErrorOnNode(node.Dimension, diagnostic.M_Only_in_New_Array_Expression_can_we_have_dimensions)
			return true
		}

		if ast.IsArrayType(node.Element) && isNull(node.Element.(*ast.ArrayType).Dimension) {
			c.grammarErrorOnNode(node.Dimension, diagnostic.M_Cannot_specify_an_array_dimension_after_an_empty_dimension)
			return true
		}
	}

	return false
}

func (c *checker) grammarErrorAfterFirstToken(node ast.Node, message *diagnostic.Message, args ... interface{})  bool {
	var sourceFile = ast.GetSourceFileOfNode(node)
	if !c.hasParseDiagnostics(sourceFile) {
		var _span = utilities.GetSpanOfTokenAtPosition(sourceFile, node.Pos())
		c.diagnostics = append(c.diagnostics, diagnostic.CreateFileDiagnostic(sourceFile, span.TextSpanEnd(_span), 0, message, args...))
		return true
	}
	return false
}